#include <sstream>
#include <iostream>
#include <vector>
#include "miniutil/date.h"
#include "miniutil/address.h"
#include "miniutil/strutil.h"
#include "miniutil/os_compatible.h"
#include "miniutil/mhttpclient.h"


namespace miniutil { namespace multinet {

static std::string GetMimeType(std::string filename)
{
	std::string::size_type pos = filename.find_last_of(".");
	std::string suffix = filename.substr(pos+1);
	if(suffix == "txt" || suffix == "html" || suffix == "htm" ||suffix == "text")
		return std::string("text/html");
	if(suffix == "torrent")
		return std::string("application/x-bittorent");
	return std::string("application/oct-stream");
}

//080601 by liu.l
//NOTE, just support one file upload, if more files want to be uploaded, refer the
//http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
// I have not tested if current implement support multifile upload

static int calculate_formdatalength(std::string boundary, std::vector<httpformdata> &vformdata)
{
	int contentlen = 0;
	std::string svalue;
	for(int j=0; j<(int)vformdata.size(); j++)
	{
		if(vformdata[j].type == "text")
		{
			svalue = "--"+boundary+"\r\nContent-Disposition: form-data; name="+vformdata[j].name
				+ "\r\n"+vformdata[j].value+ "\r\n\r\n";
			contentlen += (int)svalue.length();
		}
		else if(vformdata[j].type == "file")
		{
			std::string filename;
			std::string::size_type pos = vformdata[j].value.find_last_of("/");
			if(pos == std::string::npos)
				pos = vformdata[j].value.find_last_of("\\");
			if(pos == std::string::npos)
				filename = vformdata[j].value;
			else
				filename = vformdata[j].value.substr(pos+1);

			svalue = "--"+boundary+"\r\nContent-Disposition: form-data; name=";
			svalue += vformdata[j].name + ";";
			svalue += "filename=" + filename + "\r\n";
			svalue += "Content-Type: "+ GetMimeType(filename) + "\r\n\r\n";
			contentlen += (int)svalue.length();

			FILE* file = fopen(vformdata[j].value.c_str(), "rb");
			if(file != NULL)
			{
				fseek(file, 0, SEEK_END);
				contentlen += ftell(file);
				fclose(file);
			}
			svalue = "\r\n";
			contentlen += (int)svalue.length();

		}
	}
	svalue = "--"+boundary+"--\r\n";
	contentlen += (int)svalue.length();
	return contentlen;
}

//-----------------------------------------------------------------

httpclient::httpclient(SocketMulplexService* pss)
  :recvbuf(131074)
{
	httpsoc  = NULL;
	mpss = pss;
	
	mrespcode = 0;
	mresplen = 0;

	mcontenthaverecv = 0;
	mstatus = STUS_NONE;

	havereadheader = 0;
	reqheaders["User-Agent"] = "MHTTPClient/0.1";

	mltmstactive = 0;

	mbytefrom = -1;
	mbyteto = -1;

	timeoutms = 10000;
}

httpclient::~httpclient()
{
	if(httpsoc != NULL)
	{
		mpss->del_socket(httpsoc);
		closesocket(httpsoc);
	}
}

int httpclient::do_GET(std::string urlstr_in, std::string method_in, 
					   int from_in, int to_in,
					   int timeoutms_in)
{
  if(mstatus != STUS_NONE && mstatus != STUS_FIN && mstatus != STUS_FAIL)
    return -1;
  
  if(urlstr_in.find("http") != std::string::npos)
	urlstr =   urlstr_in;
  else
	 urlstr = std::string("http://")+urlstr_in;

	method = method_in;
	timeoutms = timeoutms_in;
	mstatus = STUS_NONE;

	httpURL.parseURL(urlstr);
	if(httpURL.port == 0)
		httpURL.port = 80;

  //---------create header-----------------
	mreqheader = "";
	mreqheader = method + " " + httpURL.uri;
	if ( httpURL.getQueryString().length() > 0 )
		mreqheader += "?"+httpURL.getQueryString();
	mreqheader += " HTTP/1.0\r\nAccept: */*\r\nHost: " + httpURL.host + "\r\n";

	if(from_in >= 0)
	{
		std::string range;
		if(to_in > from_in)
		{
			range =  miniutil::util::toString(from_in) + "-" + miniutil::util::toString(to_in) ;
		}
		else
		{
			range =  miniutil::util::toString(from_in) + "-";
		}
		mreqheader += std::string("Range: bytes=")+range+"\r\n";
		mbytefrom = from_in;
		mbyteto = to_in;
	}

	map<string,string>::iterator i = reqheaders.begin();
	while( i != reqheaders.end() )
	{
		mreqheader += i->first + ": " + i->second + "\r\n";
		i++;
	}

	mreqheader += "Connection: Close\r\n\r\n";
	
  return 0;
}

int httpclient::do_POSTFORM_URL(std::string urlstr_in, std::vector<httpformdata> &vformdata_in, int timeoutms_in)
{
  if(mstatus != STUS_NONE && mstatus != STUS_FIN && mstatus != STUS_FAIL)
    return -1;
    
  urlstr =   urlstr_in;
  method = "POST_URL";
  timeoutms = timeoutms_in;
  vformdata = vformdata_in;
  mstatus = STUS_NONE;

  httpURL.parseURL(urlstr);
  if(httpURL.port == 0)
    httpURL.port = 80;

  //---------create header-----------------
  mreqheader = "";
	mreqheader = std::string("POST") + " " + httpURL.uri;
	mreqheader += " HTTP/1.0\r\nAccept: */*\r\nHost: " + httpURL.host + "\r\n";
	mreqheader += " Content-Type: application/x-www-form-urlencoded\r\n" ;
	
	map<string,string>::iterator i = reqheaders.begin();
	while( i != reqheaders.end() )
	{
		mreqheader += i->first + ": " + i->second + "\r\n";
		i++;
	}
	mreqheader += "Connection: Close\r\n\r\n";

	if(vformdata_in.size() > 0)
	{
		std::string orformdata, tgtformdata;
		for(size_t j=0; j<vformdata_in.size(); j++)
		{
			orformdata += (vformdata_in[j].name + "=" +vformdata_in[j].value);
			if(j < vformdata_in.size() - 1)
				orformdata += "&";
		}
		tgtformdata = url::encode(orformdata);
		mreqheader += tgtformdata + "\r\n\r\n";
	}
	else
	{
		mreqheader += "\r\n";
	}

  return 0;
}

int httpclient::do_POSTFORM_MULPART(std::string urlstr_in, std::vector<httpformdata> &vformdata_in, int timeoutms_in)
{
  if(mstatus != STUS_NONE && mstatus != STUS_FIN && mstatus != STUS_FAIL)
    return -1;
    
  urlstr =   urlstr_in;
  method = "POST_MULPART";
  timeoutms = timeoutms_in;
  vformdata = vformdata_in;
  mstatus = STUS_NONE;

  httpURL.parseURL(urlstr);
  if(httpURL.port == 0)
    httpURL.port = 80;
  
  //---------create header-----------------
  //-todo--- calculate form length-- create header and so on..
	std::string boundary;
	boundary = "---------------------------7d2143d300136";
	int contentlen = 0;
	contentlen = calculate_formdatalength(boundary, vformdata);
	
	std::stringstream ss;
	ss<<"POST "<<httpURL.uri<<" HTTP/1.0\r\n";
	ss<<"Accept: */*\r\n";
    ss<<"Host: "<<httpURL.host<<"\r\n";
	ss<<"Content-Type: multipart/form-data; boundary="<<boundary<<"\r\n";
	ss<<"Content-Length: "<<contentlen<<"\r\n";

	map<string,string>::iterator i = reqheaders.begin();
	while( i != reqheaders.end() )
	{
		ss<<i->first << ": " << i->second << "\r\n";
		i++;
	}
	ss << "Connection: Close\r\n\r\n";	
	mreqheader = ss.str();

	//mreqheader = "";
	//mreqheader = std::string("POST") + " " + httpURL.uri;
	//mreqheader += " HTTP/1.0\r\nAccept: */*\r\nHost: " + httpURL.host + "\r\n";
	//mreqheader += "Content-Type: multipart/form-data; boundary="+boundary+"\r\n" ;
	//mreqheader += "Content-Length: "+std::string(lenbuf)+"\r\n" ;
	//
	//map<string,string>::iterator i = reqheaders.begin();
	//while( i != reqheaders.end() )
	//{
	//	mreqheader += i->first + ": " + i->second + "\r\n";
	//	i++;
	//}
	//mreqheader += "Connection: Close\r\n\r\n";	
  
  return 0;
}

int httpclient::working()
{
	if(httpURL.urlstr.empty())
		return 0;

  int ret = 0;
  miniutil::m_uint32_t now = miniutil::date::currentSec();
  miniutil::auto_lock k(&mclientmtx); 
  switch(mstatus)
  {
  case(STUS_NONE):
    working_status_none();
    break;
  case(STUS_CONNETING):
    working_status_connecting();
    break;
  case(STUS_CONNECTED):
    working_status_connected();
	break;
  case(STUS_REQDONE):
    working_status_reqdone();
    break;
  case(STUS_RESP_HEADER):
    working_status_headerdone();
    break;
  default:
    if(httpsoc != NULL)
    {
        mpss->del_socket(httpsoc);
        closesocket(httpsoc);
        httpsoc = NULL;
    }
	ret = 1;
    break;
  }

    if(mstatus != STUS_FAIL && mstatus != STUS_NONE && mstatus != STUS_FIN)
    {
        if(now > mltmstactive + timeoutms/1000)
        {
            mstatus = STUS_TIMEOUT;
            if(httpsoc != NULL)
            {
                mpss->del_socket(httpsoc);
                closesocket(httpsoc);
                httpsoc = NULL;
            }
            ret = 1;
        }
    }

  return ret;
}

void httpclient::working_status_none()
{
	if(httpsoc != NULL)
	{
		mpss->del_socket(httpsoc);
		closesocket(httpsoc);
	}
	
	miniutil::ip4addr hostip(httpURL.host);
	miniutil::soaddr peer = soaddr( httpURL.port,hostip );
	if ( hostip.isInetAny() )
    {
		std::cout<<"param(host) is invalid!"<<httpURL.host<<std::endl;
        mstatus = STUS_FAIL;
		return;
    }

    //struct sockaddr_in serveraddr;
    //memset( &serveraddr, 0, sizeof(struct sockaddr_in) );
    //serveraddr.sin_family = AF_INET;
    //serveraddr.sin_addr.s_addr = inet_addr(httpURL.host.c_str());
    //serveraddr.sin_port = htons(httpURL.port);

    httpsoc = ::socket(PF_INET, SOCK_STREAM, 0);
    //asyn
    int namelen = sizeof(struct sockaddr);
#ifdef WIN32
    unsigned long on = 1;
    ioctlsocket(httpsoc, FIONBIO, &on);
#else
	//TODO
#endif

    int ret = ::connect(httpsoc, peer.get_sockaddr(), namelen);
	if(ret == -1)
	{
	#ifdef WIN32
		int sret = WSAGetLastError();
		if(sret != WSAEWOULDBLOCK)
		{
			std::cout<<"httpclient:connect error!"<<std::endl;
			mstatus = STUS_FAIL;
			return;
		}
	#endif
	}
    mpss->add_tcp_socket(httpsoc, miniutil::multinet::EVT_CONNECTED);
    mstatus = STUS_CONNETING;
    
    mcontenthaverecv = 0;
	havereadheader = 0;

	mltmstactive = miniutil::date::currentSec();
}

void httpclient::working_status_connecting()
{
    if( mpss->is_connected(httpsoc))
	{
		mstatus = STUS_CONNECTED;
		mpss->add_tcp_socket(httpsoc, miniutil::multinet::EVT_WRITE_READY);
		mltmstactive = miniutil::date::currentSec();
	}
	else if(mpss->is_exception(httpsoc) || mpss->is_closed(httpsoc))
	{
		mstatus = STUS_FAIL;
	}
}

void httpclient::working_status_connected()
{
	int ret;
    if(mpss->is_writeable(httpsoc) )
    {
		ret = mpss->send(httpsoc, mreqheader.c_str(), (int)mreqheader.length());
		if(ret == -1)
			mstatus = STUS_FAIL;
		else
			mstatus = STUS_REQDONE;
		mpss->add_tcp_socket(httpsoc, miniutil::multinet::EVT_READ_READY);
		mrespheader = "";
		mltmstactive = miniutil::date::currentSec();
    }
}

void httpclient::working_status_reqdone()
{
    int ret;
    if(mpss->is_readable(httpsoc))
    {
        if(sizeof(headerbuf)-havereadheader > 0)
            ret = mpss->recv(httpsoc, headerbuf, sizeof(headerbuf)-havereadheader);
        else
        {
            //too large header, fail
            std::cout<<"header learger than "<<sizeof(headerbuf)<<std::endl;
            mstatus = STUS_FAIL;
            return;
        }
        if(ret < 0)
        {
            mstatus = STUS_FAIL;
            return ;
        }
        else if(ret == 0)
        {
            mstatus = STUS_FIN;
            return;
        }
        
        havereadheader += ret;
        int headerseperatornum = 0;
        char* headerend = strstr(headerbuf, "\r\n\r\n");
        if(headerend != NULL)
            headerseperatornum = 4;
        else
        {
            headerend = strstr(headerbuf, "\n\n"); //0x0a
            if(headerend != NULL)
            headerseperatornum = 2;
        }
        
        if(headerend  != NULL)
        {
            std::pair<char*, unsigned int> writepos;
            recvbuf.getwritepos(writepos);
            int copysize = havereadheader - (headerend - headerbuf) - headerseperatornum;
            memcpy(writepos.first, headerend+headerseperatornum, copysize);
            recvbuf.more_content(copysize);
            mcontenthaverecv = copysize;
            
            mrespheader = std::string(headerbuf, headerend-headerbuf);
            M_DEBUG("%s", mrespheader.c_str());
            analyze_header();
            mstatus = STUS_RESP_HEADER;
        }
        mltmstactive = miniutil::date::currentSec();
    
    }
}

void httpclient::working_status_headerdone()
{
	int ret;
	while(mpss->is_readable(httpsoc))
	{
		std::pair<char*, unsigned int> writepos;
		recvbuf.getwritepos(writepos);
		if(writepos.second <= 0)
			return;
		ret = mpss->recv(httpsoc, writepos.first, writepos.second);
		if(ret < 0)
		{
			mstatus = STUS_FAIL;
			std::cout<<"[httpclient::working_status_headerdone]"<<urlstr<<" read fail:"<<mresplen<<" "<<mcontenthaverecv<<std::endl;
            break;
		}
		else if(ret == 0)
		{
			mstatus = STUS_FIN;
			std::cout<<"[httpclient::working_status_headerdone]"<<urlstr<<" read finish:"<<mresplen<<" "<<mcontenthaverecv<<std::endl;
            break;
		}
		else
		{
			recvbuf.more_content(ret);
			mcontenthaverecv += ret;
			if(mresplen != 0x00 && mresplen == mcontenthaverecv)
			{
				std::cout<<"[httpclient::working_status_headerdone]"<<urlstr<<" read enough data: "<<mcontenthaverecv<<std::endl;
				mstatus = STUS_FIN;
                break;
			}
		}
		mltmstactive = miniutil::date::currentSec();
	}
}


int httpclient::analyze_header()
{
	std::vector<std::string> vstrs = miniutil::strutil::split(mrespheader, "\r\n");

	string::size_type sep = vstrs[0].find(' ');
	if ( sep == string::npos )
		return false;
	mrespcode = atoi( vstrs[0].substr(sep+1, 3).c_str() );
  
	mresplen = 0x70000000; //max default length.

	for(int i=1; i< (int)vstrs.size(); i++)
	{
		std::string &headerLine = vstrs[i];
		sep = headerLine.find(':');
		if ( sep == string::npos || sep == 0 )
			continue;
		string key = headerLine.substr(0,sep);
		string value = headerLine.substr(sep+1);

		this->respheaders[key] = value;
		if ( strcasecmp( key.c_str(), "Content-Length") == 0 )
			mresplen = atoi( value.c_str() );
		else if ( strcasecmp(key.c_str(), "Content-Type") == 0 )
			mresptype = value;
	}
	return 0;  
}
  
int httpclient::get_resp_content(char* pbuf, int len)
{
	if(mstatus != STUS_FIN && mstatus != STUS_RESP_HEADER && mstatus != STUS_FAIL)
		return -1;
	miniutil::auto_lock k(&mclientmtx);
	return recvbuf.read(pbuf, len);
}
  

} }
